Mac App StoreでリリースされているMac用アプリケーションをAnsibleで管理しよう
こんにちは。サービスグループの武田です。
ここ最近Ansibleしか触ってません。いや嘘です。
Ansibleで管理するMacのアプリケーションがHomebrewなどで完結する場合はいいのですが、やはりMac App Store(以下、App Store)経由でインストールしなければいけないものも出てきます。代表格がXcodeでしょうか。というわけで、今回はAnsibleを使ってApp Storeのアプリケーションを管理する方法を調べてみました。
事前の準備
プレイブックの中でmas
というCLIツールを使用しているのですが、App Storeへのサインインがコマンド経由ではできませんでした(OSのバージョン依存?)。そのため、事前にApp Storeを開き、サインインをしておく必要があります。
環境
次の環境で検証しました。
$ sw_vers ProductName: Mac OS X ProductVersion: 10.15.3 BuildVersion: 19D76 $ ansible-playbook --version | grep -E 'playbook|version' ansible-playbook 2.9.3 executable location = /usr/local/bin/ansible-playbook python version = 3.8.1 (default, Dec 27 2019, 18:06:00) [Clang 11.0.0 (clang-1100.0.33.16)]
またディレクトリの構成は次のようになっています。
$ tree . ├── ansible.cfg ├── hosts ├── roles │ └── mas │ ├── tasks │ │ └── main.yml │ └── vars │ └── main.yml └── site.yml 4 directories, 5 files
周辺ファイル
先に本質ではない部分のファイルをざっと紹介します。
ansible.cfgは全体の設定をするファイルです。今回はinterpreter_python
のみ指定しています。
[defaults] interpreter_python=/usr/bin/python3
インベントリファイルです。名前は任意ですが、今回はhosts
としました。
[localhost] 127.0.0.1
プレイブックの本体です。これも名前は任意ですが、今回はsite.yml
としました。アプリケーションの管理はロールとして切り出したため、roles
で指定しています。
--- - name: ansible hosts: localhost connection: local become: no gather_facts: no roles: - mas
App Storeアプリケーションをインストールするプレイブック
それでは本題のプレイブックです。まずApp Storeでインストールしたいアプリケーションのリストは変数ファイルに分けています(後述)。ところで、App Storeの管理ができるモジュールはAnsibleでは提供されていません。そのためmas
コマンドを使用することにします。CLIでApp Storeのアプリケーションを操作できるもので、Homebrewでインストールできます(mas
のインストールは別ロールに分けてもOK)。
プレイブックを作成する際に、基本的には提供されているモジュールが冪等性を確保してくれますが、command/shellモジュールを使用する場合は自分で確保しなければいけません。そのためmas install
を実行する前に、name: fetch list
でインストール済みのアプリケーション一覧を取得し、これを使用して実際にコマンドを実行するかの条件を記述しています。whenの条件がやや複雑ですが、取得した一覧に管理対象のアプリケーションがあるかを探索し、なければコマンドを実行するという条件です。select
はジェネレータを返すため| list
でリストにし、その長さをチェックしています。
--- - block: - name: install mas cli homebrew: name: mas state: present - name: fetch list command: mas list register: installed_list check_mode: no changed_when: no - name: install App Store applications command: "mas install {{ item.id }}" when: "installed_list.stdout_lines | select('search', item.id) | list | length == 0" loop: "{{ apps }}" tags: - mas
インストールしたいアプリケーションは変数ファイルに記述します。id
だけでも動きますが、番号だけではどれがどれだかわからなくなってしまうためname
も書くようにしています。ちなみにアプリケーションのIDはApp Storeをブラウザで開き、対象アプリケーションのページにいくとURLでわかります。
--- apps: - { name: "Xcode", id: "497799835" } - { name: "Keynote", id: "409183694" } - { name: "Numbers", id: "409203825" } - { name: "Pages", id: "409201541" }
ちなみに、installed_list
は次のような内容になっています。
"installed_list": { "changed": false, "cmd": [ "mas", "list" ], "delta": "0:00:00.042378", "end": "2020-02-18 12:00:00.000000", "failed": false, "rc": 0, "start": "2020-02-18 12:00:00.000000", "stderr": "", "stderr_lines": [], "stdout": "409183694 Keynote (9.2.1)\n497799835 Xcode (11.3.1)\n409203825 Numbers (6.2.1)", "stdout_lines": [ "409183694 Keynote (9.2.1)", "497799835 Xcode (11.3.1)", "409203825 Numbers (6.2.1)" ] }
実行してみる
書いたプレイブックを実行すると次のようになりました。
$ ansible-playbook -i hosts site.yml PLAY [ansible] ************************************************************************************* TASK [mas : install mas cli] *********************************************************************** ok: [127.0.0.1] TASK [mas : fetch list] **************************************************************************** ok: [127.0.0.1] TASK [mas : install App Store applications] ******************************************************** skipping: [127.0.0.1] => (item={'name': 'Xcode', 'id': 497799835}) skipping: [127.0.0.1] => (item={'name': 'Keynote', 'id': 409183694}) skipping: [127.0.0.1] => (item={'name': 'Numbers', 'id': 409203825}) changed: [127.0.0.1] => (item={'name': 'Pages', 'id': 409201541}) PLAY RECAP ***************************************************************************************** 127.0.0.1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
未インストールのアプリケーションはインストールされ、インストール済みのものはちゃんとskipされていますね!(whenを消すとすべてchangedになるので試してみてください)
まとめ
AnsibleでApp Storeのアプリケーションを管理する方法について紹介しました。アプリケーションのインストールには時間がかかるので、プレイブックを実行したらコーヒーでも飲んでゆっくり待ちましょう。